import math


class String:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, cls):
        # print('cls', cls)
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        #  print(instance, value)
        # print('self.name', self.name)
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        instance.__dict__[self.name] = value


class Integer:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]

    def __set__(self, instance, value: int):
        if not isinstance(value, int):
            raise TypeError('Expected an int')
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]


class Person:
    name = String('name')
    age = Integer('age')

    def __init__(self, name, age):
        self.name = name
        self.age = age


class Typed:
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type

    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        # print(value, self.expected_type)
        if not isinstance(value, self.expected_type):
            raise TypeError('Expected a {}'.format(self.expected_type))
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]


def descriptor_test():
    p1 = Person('vincent', age=30)
    print(p1.name)  # Person.name.__get__(p1, Person)
    print(p1.name is Person.name.__get__(p1, Person))
    print(p1.age is Person.age.__get__(p1, Person))
    print(Person.age.__get__(p1, Person))


def type_assert(**kwargs):
    print(kwargs)

    def decorator(cls):
        print(cls.__name__)
        for name, expected_type in kwargs.items():
            setattr(cls, name, Typed(name, expected_type))
        return cls

    return decorator


@type_assert(name=str, shares=int, price=float)
class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

    pass


class lazy_property:
    def __init__(self, func):
        print(func)
        self.func = func

    def __get__(self, instance, cls):
        # print(instance, cls)
        if instance is None:
            return self
        else:
            value = self.func(instance)
            setattr(instance, self.func.__name__, value)
            return value


class Circle:
    def __init__(self, radius):
        self.radius = radius

    @lazy_property
    def area(self):
        print('Compute area of circle', self.radius)
        return math.pi * self.radius ** 2

    @lazy_property
    def perimeter(self):
        return 2 * math.pi * self.radius


if __name__ == '__main__':
    # descriptor_test()
    # Stock('vincent',20,20.2)

    circle = Circle(radius=4)
    print(circle.area)

    print(circle.area)
    print(circle.perimeter)
    print(circle.__dict__)
